namespace Radar
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;

    public static class ContainerExtensions
    {
        public delegate bool TypeMatchPredicate(Type serviceType, Type implementationType);

        public static IContainer RegisterAllFrom(this IContainer container, Assembly assembly)
        {
            return container.RegisterAllFrom(assembly, (_, __) => true);
        }

        public static IContainer RegisterAllFrom(this IContainer container, Assembly assembly, TypeMatchPredicate predicate)
        {
            return container.Register(assembly, true, predicate);
        }

        public static IContainer RegisterSingleFrom(this IContainer container, Assembly assembly, TypeMatchPredicate predicate)
        {
            var types = assembly
                .GetTypes()
                .Where(x => IsSuitable(x) && predicate(x, x));

            foreach (var type in types)
                container.Register(type, type);

            return container;
        }

        public static IContainer Register(this IContainer container,
            Assembly assembly,
            bool includeNonPublic,
            TypeMatchPredicate predicate)
        {
            var typePairs = GetTypesFrom(assembly, includeNonPublic, predicate);

            foreach (var pair in typePairs)
            {
                if (container.IsRegistered(pair.Key)) continue;

                container.Register(pair.Key, pair.Value);
            }

            return container;
        }

        private static IEnumerable<KeyValuePair<Type, Type>> GetTypesFrom(Assembly assembly,
            bool includeNonPublic,
            TypeMatchPredicate predicate)
        {
            var types = includeNonPublic ? assembly.GetTypes() : assembly.GetExportedTypes();

            return types
                .Where(IsSuitable)
                .Select(x => new KeyValuePair<Type, Type>(GetServiceType(x), x))
                .Where(x => predicate(x.Key, x.Value));
        }

        private static bool IsSuitable(Type type)
        {
            return !(type.IsAbstract || type.IsInterface);
        }

        private static Type GetServiceType(Type type)
        {
            var interfaces = type.GetInterfaces();
            if (interfaces.Length > 0) return interfaces[0];

            var baseType = type.BaseType;
            if (baseType != null && baseType != typeof(object))
                return baseType;

            return type;
        }
    }
}